#include "item/rectangleitem.h"

#include <QStyleOptionGraphicsItem>
#include <QPainter>

// TODO: forbid objects to have write access to handles
// TODO: Like Inkscape, if one round handle is in the corner, but not the other,
//       then use same roundness for both
// FIXME: Roundness handles are missplaced when the rect is not normal

namespace SymbolEditor
{

    GraphicsRectangleItem::GraphicsRectangleItem(GraphicsItem *parent):
        GraphicsItem(parent), m_rect(QRectF(0, 0, 0, 0)),
        m_xRoundness(0), m_yRoundness(0)
    {
        addHandle(TopLeft, Handle::Role::FDiagSize, Handle::Shape::Diamond);
        addHandle(BottomRight, Handle::Role::FDiagSize, Handle::Shape::Diamond);
        addHandle(Bottom, Handle::Role::VSize, Handle::Shape::Diamond);
        addHandle(Left, Handle::Role::HSize, Handle::Shape::Diamond);
        addHandle(XRoundness, Handle::Role::HSize, Handle::Shape::Circle);
        addHandle(YRoundness, Handle::Role::VSize, Handle::Shape::Circle);
        updateSizeHandles();
        updateRoundnessHandles();
    }

    GraphicsRectangleItem::~GraphicsRectangleItem()
    {

    }

    QRectF GraphicsRectangleItem::rect() const
    {
        return m_rect;
    }

    void GraphicsRectangleItem::setRect(const QRectF &rect)
    {
        prepareGeometryChange();
        m_rect = rect;
        m_boundingRect = QRectF();
        update();
        updateSizeHandles();
        updateRoundnessHandles();
    }

    void GraphicsRectangleItem::setRoundness(qreal xRoundness, qreal yRoundness)
    {
        setXRoundness(xRoundness);
        setYRoundness(yRoundness);
    }

    qreal GraphicsRectangleItem::xRoundness() const
    {
        return m_xRoundness;
    }

    void GraphicsRectangleItem::setXRoundness(qreal roundness)
    {
        qreal xRoundness = qMin(qMax(roundness, 0.0), 100.0);
        if (qFuzzyCompare(xRoundness, m_xRoundness))
        {
            updateRoundnessHandles();
            return;
        }

        prepareGeometryChange();
        m_xRoundness = xRoundness;
        update();
        updateRoundnessHandles();
    }

    qreal GraphicsRectangleItem::yRoundness() const
    {
        return m_yRoundness;
    }

    void GraphicsRectangleItem::setYRoundness(qreal roundness)
    {
        qreal yRoundness = qMin(qMax(roundness, 0.0), 100.0);
        if (qFuzzyCompare(yRoundness, m_yRoundness))
        {
            updateRoundnessHandles();
            return;
        }

        prepareGeometryChange();
        m_yRoundness = yRoundness;
        update();
        updateRoundnessHandles();
    }

    void GraphicsRectangleItem::updateSizeHandles()
    {
        blockItemNotification();
        qreal midX = m_rect.right() - m_rect.width() / 2.0;
        qreal midY = m_rect.bottom() - m_rect.height() / 2.0;
        m_idToHandle[TopLeft]->setPos(m_rect.topLeft());
        m_idToHandle[BottomRight]->setPos(m_rect.bottomRight());
        m_idToHandle[Bottom]->setPos(QPointF(midX, m_rect.bottom()));
        m_idToHandle[Left]->setPos(QPointF(m_rect.left(), midY));
        if (m_rect == m_rect.normalized())
        {
            m_idToHandle[TopLeft]->setHandleRole(Handle::Role::FDiagSize);
            m_idToHandle[BottomRight]->setHandleRole(Handle::Role::FDiagSize);
        }
        else
        {
            m_idToHandle[TopLeft]->setHandleRole(Handle::Role::BDiagSize);
            m_idToHandle[BottomRight]->setHandleRole(Handle::Role::BDiagSize);
        }
        unblockItemNotification();
    }

    void GraphicsRectangleItem::updateRoundnessHandles()
    {
        blockItemNotification();
        QRectF rect = m_rect.normalized();
        QPointF xPos(rect.right() - (m_xRoundness / 100.0) * (m_rect.width()  / 2.0), rect.top());
        m_idToHandle[XRoundness]->setPos(xPos);
        QPointF yPos(rect.right(), rect.top() + (m_yRoundness / 100.0) * (m_rect.height() / 2.0));
        m_idToHandle[YRoundness]->setPos(yPos);
        unblockItemNotification();
    }

    GraphicsItem *GraphicsRectangleItem::clone()
    {
        GraphicsRectangleItem *item = new GraphicsRectangleItem();
        GraphicsItem::cloneTo(item);
        item->setRect(rect());
        item->setXRoundness(m_xRoundness);
        item->setYRoundness(m_yRoundness);
        return item;
    }

    void GraphicsRectangleItem::itemNotification(IObservableItem *item)
    {
        Handle *handle = dynamic_cast<Handle *>(item);
        Q_ASSERT(handle);

        int id = handle->handleId();
        QRectF rect = m_rect;
        QRectF normedRect = m_rect.normalized();
        switch (id)
        {
            case XRoundness:
                setXRoundness(100.0 * (normedRect.right() - handle->pos().x()) / (normedRect.width() / 2.0));
                return;
            case YRoundness:
                setYRoundness(100.0 * (handle->pos().y() - normedRect.top()) / (normedRect.height() / 2.0));
                return;
            case TopLeft:
                rect.setTopLeft(handle->pos());
                break;
            case BottomRight:
                rect.setBottomRight(handle->pos());
                break;
            case Bottom:
                rect.setBottom(handle->pos().y());
                break;
            case Left:
                rect.setLeft(handle->pos().x());
                break;
            default:
                Q_ASSERT(false);
                return;
        }
        setRect(rect);
    }

    QList<PropertyId> GraphicsRectangleItem::propertyIdList() const
    {
        auto result = GraphicsItem::propertyIdList();
        result << WidthProperty
               << HeightProperty;
        return result;
    }

    void GraphicsRectangleItem::setProperty(PropertyId id, const QVariant &value)
    {
        switch (id)
        {
            case WidthProperty:
                setRect(QRectF(0.0, 0.0, value.toReal(), m_rect.height()));
                break;
            case HeightProperty:
                setRect(QRectF(0.0, 0.0, m_rect.width(), value.toReal()));
                break;
            // FIXME: X/YRoundness
            default:
                GraphicsItem::setProperty(id, value);
                break;
        }

    }

    QVariant GraphicsRectangleItem::property(PropertyId id) const
    {
        switch (id)
        {
            case WidthProperty:
                return m_rect.width();
            case HeightProperty:
                return m_rect.height();
            // FIXME: X/YRoundness
            default:
                return GraphicsItem::property(id);
        }
    }

    QList<QPointF> GraphicsRectangleItem::endPoints() const
    {
        return QList<QPointF>() << m_rect.topLeft()
                                << m_rect.topRight()
                                << m_rect.bottomRight()
                                << m_rect.bottomLeft();
    }

    QList<QPointF> GraphicsRectangleItem::midPoints() const
    {

        return QList<QPointF>() << QPointF(m_rect.center().x(), m_rect.top())
                                << QPointF(m_rect.right(), m_rect.center().y())
                                << QPointF(m_rect.center().x(), m_rect.bottom())
                                << QPointF(m_rect.left(), m_rect.center().y());
    }

    QList<QPointF> GraphicsRectangleItem::centerPoints() const
    {
        return QList<QPointF>() << m_rect.center();
    }

    QList<QPointF> GraphicsRectangleItem::nearestPoints(QPointF pos) const
    {
        QRectF rect = m_rect.normalized();
        QList<QLineF> edges;
        edges << QLineF(rect.topLeft(),     rect.topRight())
              << QLineF(rect.topRight(),    rect.bottomRight())
              << QLineF(rect.bottomRight(), rect.bottomLeft())
              << QLineF(rect.bottomLeft(),  rect.topLeft());

        QList<QPointF> candidates;
        for (const QLineF &edge : edges)
        {
            candidates << nearestPoint(edge, mapFromScene(pos));
        }

        return QList<QPointF>() << nearestPoint(candidates, mapFromScene(pos));
    }

    QRectF GraphicsRectangleItem::boundingRect() const
    {
        if (m_boundingRect.isNull())
        {
            qreal halfpw = pen().style() == Qt::NoPen ? qreal(0) : pen().widthF() / 2;
            m_boundingRect = m_rect;
            if (halfpw > 0.0)
            {
                m_boundingRect.adjust(-halfpw, -halfpw, halfpw, halfpw);
            }
        }
        return m_boundingRect;

    }

    QPainterPath GraphicsRectangleItem::shape() const
    {
        QPainterPath path;
        path.addRoundedRect(m_rect, m_xRoundness, m_yRoundness, Qt::RelativeSize);
        return shapeFromPath(path, pen());
    }

    void GraphicsRectangleItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                              QWidget *widget)
    {
        Q_UNUSED(option);
        Q_UNUSED(widget);
        painter->setPen(pen());
        painter->setBrush(brush());
        painter->drawRoundedRect(m_rect, m_xRoundness, m_yRoundness, Qt::RelativeSize);
    }

    QVariant GraphicsRectangleItem::itemChange(QGraphicsItem::GraphicsItemChange change,
                                       const QVariant &value)
    {
        if (change == QGraphicsItem::ItemSelectedHasChanged)
        {
            for (Handle *handle : m_handleToId.keys())
            {
                handle->setVisible(isSelected());
            }
        }
        return value;
    }


    bool GraphicsRectangleItem::contains(const QPointF &point) const
    {
        // FIXME: Needed to avoid slowing down the view: default impl of contains() call
        // shape() and QPainterPath implement the rounded corner with bezier curves,
        // which is CPU intensive. Relying on boundingRect() is super fast, but quite
        // inacurate, find and implement a good trade off b/w speed and precision
        return boundingRect().contains(point);
    }

}
